package com.gogant.spider.core;

import java.net.URL;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.DelayQueue;
import java.util.concurrent.Delayed;
import java.util.concurrent.TimeUnit;

public class URLCache {
    private static Object NULL = new Object();
    private final Map<String, Object> urlCache = new ConcurrentHashMap<String, Object>();
    private final DelayQueue<DelayedKey> delayQueue = new DelayQueue<DelayedKey>();
    private final CacheMonitor cacheMonitor = new CacheMonitor();

    private String getUUID(String url) {
        return UUID.nameUUIDFromBytes(url.getBytes()).toString();
    }

    public boolean put(String url, long expire) {
        if (expire <= 0) {
            return true;
        }
        String key = getUUID(url);
        if (urlCache.put(key, NULL) != null) {
            return false;
        }
        delayQueue.add(new DelayedKey(key, expire));
        if (!cacheMonitor.isRunning) {
            cacheMonitor.startup();
        }
        return true;
    }

    public boolean put(URL url, long expire) {
        return put(url.toString(), expire);
    }

    public int size() {
        return urlCache.size();
    }

    public void clear() {
        urlCache.clear();
        delayQueue.clear();
        cacheMonitor.shutdown();
    }

    private class DelayedKey implements Delayed {
        private String urlKey;
        private long time;

        public DelayedKey(String urlKey, long expire) {
            this.urlKey = urlKey;
            this.time = System.nanoTime() + TimeUnit.MILLISECONDS.toNanos(expire);
        }

        @Override
        public long getDelay(TimeUnit unit) {
            return unit.convert(time - System.nanoTime(), TimeUnit.NANOSECONDS);
        }

        @Override
        public int compareTo(Delayed o) {
            long d = time - ((DelayedKey) o).time;
            return d > 0 ? 1 : (d < 0 ? -1 : 0);
        }
    }

    private class CacheMonitor implements Runnable {
        transient boolean isRunning;
        Thread cacheThread;

        @Override
        public void run() {
            while (isRunning) {
                DelayedKey key = null;
                try {
                    key = delayQueue.poll(60, TimeUnit.SECONDS);
                } catch (InterruptedException e) {
                    continue;
                }
                if (key == null) {
                    synchronized (this) {
                        if (delayQueue.size() == 0) {
                            isRunning = false;
                            return;
                        }
                    }
                } else {
                    urlCache.remove(key.urlKey);
                }
            }
        }

        protected void startup() {
            synchronized (this) {
                if (isRunning) {
                    return;
                }
                isRunning = true;
                cacheThread = new Thread(this, "CacheClearThread");
                cacheThread.setDaemon(true);
                cacheThread.start();
            }
        }

        protected void shutdown() {
            synchronized (this) {
                if (!isRunning) {
                    return;
                }
                isRunning = false;
                cacheThread.interrupt();
                try {
                    cacheThread.join();
                } catch (InterruptedException e) {
                }
            }
        }
    }

}
